package pl.allegro.tech.hermes.test.helper.builder;

import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicLong;
import pl.allegro.tech.hermes.api.BatchSubscriptionPolicy;
import pl.allegro.tech.hermes.api.ContentType;
import pl.allegro.tech.hermes.api.DeliveryType;
import pl.allegro.tech.hermes.api.EndpointAddress;
import pl.allegro.tech.hermes.api.EndpointAddressResolverMetadata;
import pl.allegro.tech.hermes.api.Header;
import pl.allegro.tech.hermes.api.MessageFilterSpecification;
import pl.allegro.tech.hermes.api.MonitoringDetails;
import pl.allegro.tech.hermes.api.OwnerId;
import pl.allegro.tech.hermes.api.Subscription;
import pl.allegro.tech.hermes.api.SubscriptionMode;
import pl.allegro.tech.hermes.api.SubscriptionName;
import pl.allegro.tech.hermes.api.SubscriptionOAuthPolicy;
import pl.allegro.tech.hermes.api.SubscriptionPolicy;
import pl.allegro.tech.hermes.api.Topic;
import pl.allegro.tech.hermes.api.TopicName;
import pl.allegro.tech.hermes.api.TrackingMode;
import pl.allegro.tech.hermes.api.subscription.metrics.SubscriptionMetricsConfig;

public class SubscriptionBuilder {

  private static final AtomicLong sequence = new AtomicLong();

  private final TopicName topicName;

  private final String name;

  private Subscription.State state = Subscription.State.PENDING;

  private EndpointAddress endpoint = EndpointAddress.of("http://localhost:12345");

  private ContentType contentType = ContentType.JSON;

  private String description = "description";

  private SubscriptionPolicy serialSubscriptionPolicy =
      new SubscriptionPolicy(100, 10, 1000, 1000, false, 100, null, 0, 1, 600);

  private BatchSubscriptionPolicy batchSubscriptionPolicy;

  private boolean trackingEnabled = false;

  private TrackingMode trackingMode = TrackingMode.TRACKING_OFF;

  private boolean http2Enabled = false;

  private boolean profilingEnabled = false;

  private long profilingThresholdMs = 0;

  private OwnerId owner = new OwnerId("Plaintext", "some team");

  private MonitoringDetails monitoringDetails = MonitoringDetails.EMPTY;

  private DeliveryType deliveryType = DeliveryType.SERIAL;

  private final List<MessageFilterSpecification> filters = new ArrayList<>();

  private SubscriptionMode mode = SubscriptionMode.ANYCAST;

  private final List<Header> headers = new ArrayList<>();

  private EndpointAddressResolverMetadata metadata = EndpointAddressResolverMetadata.empty();
  private SubscriptionOAuthPolicy oAuthPolicy;

  private boolean attachingIdentityHeadersEnabled = false;

  private boolean autoDeleteWithTopicEnabled = false;

  private SubscriptionMetricsConfig metricsConfig = null;

  private SubscriptionBuilder(
      TopicName topicName, String subscriptionName, EndpointAddress endpoint) {
    this.topicName = topicName;
    this.name = subscriptionName;
    this.endpoint = endpoint;
  }

  private SubscriptionBuilder(TopicName topicName, String subscriptionName) {
    this.topicName = topicName;
    this.name = subscriptionName;
  }

  public static SubscriptionBuilder subscriptionWithRandomName(
      TopicName topicName, String endpoint) {
    return new SubscriptionBuilder(
        topicName, "subscription" + sequence.incrementAndGet(), EndpointAddress.of(endpoint));
  }

  public static SubscriptionBuilder subscriptionWithRandomName(TopicName topicName) {
    return new SubscriptionBuilder(topicName, UUID.randomUUID().toString());
  }

  public static SubscriptionBuilder subscription(SubscriptionName subscriptionName) {
    return new SubscriptionBuilder(subscriptionName.getTopicName(), subscriptionName.getName());
  }

  public static SubscriptionBuilder subscription(TopicName topicName, String subscriptionName) {
    return new SubscriptionBuilder(topicName, subscriptionName);
  }

  public static SubscriptionBuilder subscription(Topic topic, String subscriptionName) {
    return new SubscriptionBuilder(topic.getName(), subscriptionName);
  }

  public static SubscriptionBuilder subscription(
      TopicName topicName, String subscriptionName, EndpointAddress endpoint) {
    return new SubscriptionBuilder(topicName, subscriptionName, endpoint);
  }

  public static SubscriptionBuilder subscription(
      String topicQualifiedName, String subscriptionName) {
    return new SubscriptionBuilder(
        TopicName.fromQualifiedName(topicQualifiedName), subscriptionName);
  }

  public static SubscriptionBuilder subscription(
      String topicQualifiedName, String subscriptionName, String endpoint) {
    return subscription(
        TopicName.fromQualifiedName(topicQualifiedName),
        subscriptionName,
        EndpointAddress.of(endpoint));
  }

  public static SubscriptionBuilder subscription(
      String topicQualifiedName, String subscriptionName, URI endpoint) {
    return subscription(
        TopicName.fromQualifiedName(topicQualifiedName),
        subscriptionName,
        EndpointAddress.of(endpoint));
  }

  public static SubscriptionBuilder subscription(
      String topicQualifiedName, String subscriptionName, EndpointAddress endpoint) {
    return subscription(
        TopicName.fromQualifiedName(topicQualifiedName), subscriptionName, endpoint);
  }

  public Subscription build() {
    if (deliveryType == DeliveryType.SERIAL) {
      return Subscription.createSerialSubscription(
          topicName,
          name,
          endpoint,
          state,
          description,
          serialSubscriptionPolicy,
          trackingEnabled,
          trackingMode,
          owner,
          monitoringDetails,
          contentType,
          filters,
          mode,
          headers,
          metadata,
          oAuthPolicy,
          http2Enabled,
          profilingEnabled,
          profilingThresholdMs,
          attachingIdentityHeadersEnabled,
          autoDeleteWithTopicEnabled,
          metricsConfig);
    } else {
      return Subscription.createBatchSubscription(
          topicName,
          name,
          endpoint,
          state,
          description,
          batchSubscriptionPolicy,
          trackingEnabled,
          trackingMode,
          owner,
          monitoringDetails,
          contentType,
          filters,
          headers,
          metadata,
          oAuthPolicy,
          http2Enabled,
          attachingIdentityHeadersEnabled,
          autoDeleteWithTopicEnabled);
    }
  }

  public SubscriptionBuilder withEndpoint(EndpointAddress endpoint) {
    this.endpoint = endpoint;
    return this;
  }

  public SubscriptionBuilder withEndpoint(String endpoint) {
    this.endpoint = EndpointAddress.of(endpoint);
    return this;
  }

  public SubscriptionBuilder withEndpoint(URI endpoint) {
    this.endpoint = EndpointAddress.of(endpoint.toString());
    return this;
  }

  public SubscriptionBuilder withState(Subscription.State state) {
    this.state = state;
    return this;
  }

  public SubscriptionBuilder withDescription(String description) {
    this.description = description;
    return this;
  }

  public SubscriptionBuilder withSubscriptionPolicy(BatchSubscriptionPolicy subscriptionPolicy) {
    this.batchSubscriptionPolicy = subscriptionPolicy;
    this.deliveryType = DeliveryType.BATCH;
    return this;
  }

  public SubscriptionBuilder withSubscriptionPolicy(SubscriptionPolicy subscriptionPolicy) {
    this.serialSubscriptionPolicy = subscriptionPolicy;
    this.deliveryType = DeliveryType.SERIAL;
    return this;
  }

  public SubscriptionBuilder withRequestTimeout(int timeout) {
    SubscriptionPolicy policy = this.serialSubscriptionPolicy;
    this.serialSubscriptionPolicy =
        SubscriptionPolicy.Builder.subscriptionPolicy()
            .withRate(policy.getRate())
            .withMessageTtl(policy.getMessageTtl())
            .withMessageBackoff(policy.getMessageBackoff())
            .withRequestTimeout(timeout)
            .build();
    return this;
  }

  public SubscriptionBuilder withTrackingMode(TrackingMode trackingMode) {
    this.trackingMode = trackingMode;
    this.trackingEnabled = trackingMode != TrackingMode.TRACKING_OFF;
    return this;
  }

  public SubscriptionBuilder withHttp2Enabled(boolean http2Enabled) {
    this.http2Enabled = http2Enabled;
    return this;
  }

  public SubscriptionBuilder withProfilingEnabled(boolean profilingEnabled) {
    this.profilingEnabled = profilingEnabled;
    return this;
  }

  public SubscriptionBuilder withProfilingThresholdMs(long profilingThresholdMs) {
    this.profilingThresholdMs = profilingThresholdMs;
    return this;
  }

  public SubscriptionBuilder withOwner(OwnerId owner) {
    this.owner = owner;
    return this;
  }

  public SubscriptionBuilder withMonitoringDetails(MonitoringDetails monitoringDetails) {
    this.monitoringDetails = monitoringDetails;
    return this;
  }

  public SubscriptionBuilder withDeliveryType(DeliveryType deliveryType) {
    this.deliveryType = deliveryType;
    return this;
  }

  public SubscriptionBuilder withContentType(ContentType contentType) {
    this.contentType = contentType;
    return this;
  }

  public SubscriptionBuilder withFilter(MessageFilterSpecification filter) {
    this.filters.add(filter);
    return this;
  }

  public SubscriptionBuilder withMode(SubscriptionMode mode) {
    this.mode = mode;
    return this;
  }

  public SubscriptionBuilder withHeader(String name, String value) {
    this.headers.add(new Header(name, value));
    return this;
  }

  public SubscriptionBuilder withEndpointAddressResolverMetadata(
      EndpointAddressResolverMetadata metadata) {
    this.metadata = metadata;
    return this;
  }

  public SubscriptionBuilder withOAuthPolicy(SubscriptionOAuthPolicy oAuthPolicy) {
    this.oAuthPolicy = oAuthPolicy;
    return this;
  }

  public SubscriptionBuilder withAttachingIdentityHeadersEnabled(
      boolean attachingIdentityHeadersEnabled) {
    this.attachingIdentityHeadersEnabled = attachingIdentityHeadersEnabled;
    return this;
  }

  public SubscriptionBuilder withAutoDeleteWithTopicEnabled(boolean autoDeleteWithTopicEnabled) {
    this.autoDeleteWithTopicEnabled = autoDeleteWithTopicEnabled;
    return this;
  }

  public SubscriptionBuilder withMetricsConfig(SubscriptionMetricsConfig metricsConfig) {
    this.metricsConfig = metricsConfig;
    return this;
  }
}
